using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Core
{
    /**
     * The ? operator used to get the
     * functionality of built-in unary operators
     * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
     */
    internal abstract class BuiltIn : Expression //implements Cloneable 
    {
        internal Expression Target;
        private string _key;

        private static readonly Dictionary<string, BuiltIn> Builtins = new Dictionary<string, BuiltIn>();

        static BuiltIn()
        {
            // These are the only ones we have now.
            // We throw a parse exception if it's not one of these.
            Builtins.Add("ancestors", new NodeBuiltins.AncestorsBuiltIn());
            Builtins.Add("byte", new NumericalBuiltins.ByteBuiltIn());
            Builtins.Add("c", new NumericalBuiltins.CBuiltIn());
            Builtins.Add("cap_first", new StringBuiltins.CapFirstBuiltIn());
            Builtins.Add("capitalize", new StringBuiltins.CapitalizeBuiltIn());
            Builtins.Add("ceiling", new NumericalBuiltins.CeilingBuiltIn());
            Builtins.Add("children", new NodeBuiltins.ChildrenBuiltIn());
            Builtins.Add("chop_linebreak", new StringBuiltins.ChopLinebreakBuiltIn());
            Builtins.Add("contains", new ContainsBuiltIn());
            Builtins.Add("date", new DateBuiltIn(TemplateDateModel.Date));
            Builtins.Add("datetime", new DateBuiltIn(TemplateDateModel.DateTime));
            Builtins.Add("default", new DefaultBuiltIn());
            Builtins.Add("double", new NumericalBuiltins.DoubleBuiltIn());
            Builtins.Add("ends_with", new EndsWithBuiltIn());
            Builtins.Add("eval", new StringBuiltins.EvalBuiltIn());
            Builtins.Add("exists", new ExistsBuiltIn());
            Builtins.Add("first", new SequenceBuiltins.FirstBuiltIn());
            Builtins.Add("float", new NumericalBuiltins.FloatBuiltIn());
            Builtins.Add("floor", new NumericalBuiltins.FloorBuiltIn());
            Builtins.Add("chunk", new SequenceBuiltins.ChunkBuiltIn());
            Builtins.Add("groups", new RegexBuiltins.GroupsBuiltIn());
            Builtins.Add("has_content", new HasContentBuiltIn());
            Builtins.Add("html", new HtmlBuiltIn());
            Builtins.Add("if_exists", new IfExistsBuiltIn());
            Builtins.Add("index_of", new IndexOfBuiltIn());
            Builtins.Add("int", new NumericalBuiltins.IntBuiltIn());
            Builtins.Add("interpret", new Interpret());
            Builtins.Add("is_boolean", new IsBooleanBuiltIn());
            Builtins.Add("is_collection", new IsCollectionBuiltIn());
            Builtins.Add("is_date", new IsDateBuiltIn());
            Builtins.Add("is_directive", new IsDirectiveBuiltIn());
            Builtins.Add("is_enumerable", new IsEnumerableBuiltIn());
            Builtins.Add("is_hash_ex", new IsHashExBuiltIn());
            Builtins.Add("is_hash", new IsHashBuiltIn());
            Builtins.Add("is_indexable", new IsIndexableBuiltIn());
            Builtins.Add("is_macro", new IsMacroBuiltIn());
            Builtins.Add("is_method", new IsMethodBuiltIn());
            Builtins.Add("is_node", new IsNodeBuiltIn());
            Builtins.Add("is_number", new IsNumberBuiltIn());
            Builtins.Add("is_sequence", new IsSequenceBuiltIn());
            Builtins.Add("is_string", new IsStringBuiltIn());
            Builtins.Add("is_transform", new IsTransformBuiltIn());
            Builtins.Add("j_string", new StringBuiltins.JStringBuiltIn());
            Builtins.Add("js_string", new StringBuiltins.JsStringBuiltIn());
            Builtins.Add("keys", new KeysBuiltIn());
            Builtins.Add("last_index_of", new LastIndexOfBuiltIn());
            Builtins.Add("last", new SequenceBuiltins.LastBuiltIn());
            Builtins.Add("left_pad", new LeftPadBuiltIn());
            Builtins.Add("length", new LengthBuiltIn());
            Builtins.Add("long", new NumericalBuiltins.LongBuiltIn());
            Builtins.Add("lower_case", new StringBuiltins.LowerCaseBuiltIn());
            Builtins.Add("matches", new RegexBuiltins.MatchesBuiltIn());
            Builtins.Add("namespace", new NamespaceBuiltIn());
            Builtins.Add("new", new NewBuiltIn());
            Builtins.Add("node_name", new NodeBuiltins.NodeNameBuiltIn());
            Builtins.Add("node_namespace", new NodeBuiltins.NodeNamespaceBuiltIn());
            Builtins.Add("node_type", new NodeBuiltins.NodeTypeBuiltIn());
            Builtins.Add("number", new StringBuiltins.NumberBuiltIn());
            Builtins.Add("parent", new NodeBuiltins.ParentBuiltIn());
            Builtins.Add("replace", new RegexBuiltins.ReplaceBuiltIn());
            Builtins.Add("reverse", new SequenceBuiltins.ReverseBuiltIn());
            Builtins.Add("right_pad", new RightPadBuiltIn());
            Builtins.Add("root", new NodeBuiltins.RootBuiltIn());
            Builtins.Add("round", new NumericalBuiltins.RoundBuiltIn());
            Builtins.Add("rtf", new RtfBi());
            Builtins.Add("seq_contains", new SequenceBuiltins.SeqContainsBuiltIn());
            Builtins.Add("seq_index_of", new SequenceBuiltins.SeqIndexOfBuiltIn(1));
            Builtins.Add("seq_last_index_of", new SequenceBuiltins.SeqIndexOfBuiltIn(-1));
            Builtins.Add("short", new NumericalBuiltins.ShortBuiltIn());
            Builtins.Add("size", new SizeBuiltIn());
            Builtins.Add("sort_by", new SequenceBuiltins.SortByBuiltIn());
            Builtins.Add("sort", new SequenceBuiltins.SortBuiltIn());
            Builtins.Add("split", new RegexBuiltins.SplitBuiltIn());
            Builtins.Add("starts_with", new StartsWithBuiltIn());
            Builtins.Add("string", new StringBuiltIn());
            Builtins.Add("substring", new StringBuiltins.SubstringBuiltIn());
            Builtins.Add("time", new DateBuiltIn(TemplateDateModel.Time));
            Builtins.Add("trim", new TrimBuiltIn());
            Builtins.Add("uncap_first", new StringBuiltins.UncapFirstBuiltIn());
            Builtins.Add("upper_case", new StringBuiltins.UpperCaseBuiltIn());
            Builtins.Add("url", new UrlBuiltIn());
            Builtins.Add("values", new ValuesBuiltIn());
            Builtins.Add("web_safe", Builtins["html"]); // deprecated; use ?html instead
            Builtins.Add("word_list", new StringBuiltins.WordListBuiltIn());
            Builtins.Add("xhtml", new XHtmlBuiltIn());
            Builtins.Add("xml", new XmlBuiltIn());
        }

        internal static BuiltIn NewBuiltIn(Expression target, string key, Token tok, string templateName)
        {
            BuiltIn bi = Builtins.GetValueOrDefault(key);
            if (bi == null)
            {
                string locationInfo = "Error on line " + tok.beginLine + ", column " + tok.beginColumn + ", in template " + templateName + "\n";
                var buf = new StringBuilder("Found " + key + ", expecting one of:");
                List<string> builtinKeys = Builtins.Keys.ToList();
                for (int i = 0; i < builtinKeys.Count; i++)
                {
                    buf.Append(i != builtinKeys.Count - 1 ? " " : " or ");
                    buf.Append(builtinKeys[i]);
                    if (i != builtinKeys.Count - 1)
                    {
                        buf.Append(",");
                    }
                }
                throw new ParseException(locationInfo + buf, target);
            }
            bi = (BuiltIn) bi.MemberwiseClone();
            bi.Target = target;
            bi._key = key;
            return bi;
        }

        public override string GetCanonicalForm()
        {
            return Target.GetCanonicalForm() + "?" + _key;
        }

        internal override bool IsLiteral()
        {
            return false; // be on the safe side.
        }

        protected override Expression DeepCloneImpl(string name, Expression subst)
        {
            //try {
            //    var clone = (BuiltIn) Clone();
            //    clone._target = _target.DeepClone(name, subst);
            //    return clone;
            //}
            //catch (CloneNotSupportedException e) {
            //    throw new InternalError();
            //}

            throw new NotImplementedException();
        }


        private class LengthBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                return new SimpleNumber(Target.GetStringValue(env).Length);
            }
        }

        private class DateBuiltIn : BuiltIn
        {
            private readonly int _dateType;

            internal DateBuiltIn(int dateType)
            {
                _dateType = dateType;
            }

            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateDateModel)
                {
                    var dmodel = (ITemplateDateModel) model;
                    int dtype = dmodel.GetDateType();
                    // Any date model can be coerced into its own type
                    if (_dateType == dtype)
                    {
                        return model;
                    }
                    // unknown and datetime can be coerced into any date type
                    if (dtype == TemplateDateModel.Unknown || dtype == TemplateDateModel.DateTime)
                    {
                        return new SimpleDate(dmodel.GetAsDate(), _dateType);
                    }
                    throw new TemplateException(
                        "Cannot convert " + TemplateDateModel.TypeNames[dtype]
                        + " into " + TemplateDateModel.TypeNames[_dateType], env);
                }
                // Otherwise, interpret as a string and attempt 
                // to parse it into a date.
                string s = Target.GetStringValue(env);
                return new DateParser(this, s, env);
            }

            private class DateParser : ITemplateDateModel, ITemplateMethodModel, ITemplateHashModel
            {
                private readonly DateBuiltIn _builtIn;
                private readonly string _text;
                private readonly Environment _env;
                private readonly string _defaultFormat;
                private DateTimeOffset? _cachedValue;

                internal DateParser(DateBuiltIn builtIn, string text, Environment env)
                {
                    _builtIn = builtIn;
                    _text = text;
                    _env = env;

                    string dateFormat = env.GetDateFormatObject(builtIn._dateType);
                    switch (dateFormat)
                    {
                        case "D":
                            _defaultFormat = env.GetLocale().DateTimeFormat.LongDatePattern;
                            break;
                        case "F":
                            _defaultFormat = env.GetLocale().DateTimeFormat.FullDateTimePattern;
                            break;
                        case "T":
                            _defaultFormat = env.GetLocale().DateTimeFormat.LongTimePattern;
                            break;
                        default:
                            _defaultFormat = dateFormat;
                            break;
                    }
                }

                public DateTimeOffset GetAsDate()
                {
                    if (!_cachedValue.HasValue)
                    {
                        _cachedValue = Parse(_defaultFormat);
                    }
                    return _cachedValue.Value;
                }

                public int GetDateType()
                {
                    return _builtIn._dateType;
                }

                public ITemplateModel Get(string pattern)
                {
                    return new SimpleDate(
                        Parse(_env.GetDateFormatObject(_builtIn._dateType, pattern)), _builtIn._dateType);
                }

                public object Exec(IList<string> arguments)
                {
                    if (arguments.Count != 1)
                    {
                        throw new TemplateModelException(
                            "string?" + _builtIn._key + "(...) requires exactly 1 argument.");
                    }
                    return Get(arguments[0]);
                }

                public bool IsEmpty()
                {
                    return false;
                }

                private DateTimeOffset Parse(string df)
                {
                    DateTimeOffset dateTime;
                    if (!DateTimeOffset.TryParseExact(_text, df, _env.GetLocale().DateTimeFormat, DateTimeStyles.AllowWhiteSpaces, out dateTime))
                    {
                        string mess = "Error: " + _builtIn.GetStartLocation()
                            + "\nExpecting a date here, found: " + _text;
                        throw new TemplateModelException(mess);
                    }
                    return dateTime;
                }
            }
        }

        private class StringBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateNumberModel)
                {
                    return new NumberFormatter(EvaluationUtil.GetNumber((ITemplateNumberModel) model, Target, env), env);
                }
                if (model is ITemplateDateModel)
                {
                    var dm = (ITemplateDateModel) model;
                    int dateType = dm.GetDateType();
                    return new DateFormatter(EvaluationUtil.GetDate(dm, Target, env), dateType, env);
                }
                if (model is SimpleScalar)
                {
                    return model;
                }
                if (model is ITemplateBooleanModel)
                {
                    return new BooleanFormatter((ITemplateBooleanModel) model, env);
                }
                if (model is ITemplateScalarModel)
                {
                    return new SimpleScalar(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "number, date, or string");
            }

            private class NumberFormatter : ITemplateScalarModel, ITemplateHashModel, ITemplateMethodModel
            {
                private readonly Number _number;
                private readonly Environment _env;
                private readonly string _defaultFormat;
                private string _cachedValue;

                internal NumberFormatter(Number number, Environment env)
                {
                    _number = number;
                    _env = env;
                    _defaultFormat = env.GetNumberFormatObject(env.GetNumberFormat());
                }

                public string GetAsString()
                {
                    if (_cachedValue == null)
                    {
                        _cachedValue = _number.DecimalValue().ToString(_defaultFormat, _env.GetLocale());
                    }
                    return _cachedValue;
                }

                public ITemplateModel Get(string key)
                {
                    return new SimpleScalar(
                        _number.DecimalValue().ToString(
                            _env.GetNumberFormatObject(key),
                            _env.GetLocale()));
                }

                public object Exec(IList<string> arguments)
                {
                    if (arguments.Count != 1)
                    {
                        throw new TemplateModelException(
                                "number?string(...) requires exactly 1 argument.");
                    }
                    return Get(arguments[0]);
                }

                public bool IsEmpty()
                {
                    return false;
                }
            }

            private class DateFormatter : ITemplateScalarModel, ITemplateHashModel, ITemplateMethodModel
            {
                private readonly DateTimeOffset _date;
                private readonly int _dateType;
                private readonly Environment _env;
                private readonly string _defaultFormat;
                private string _cachedValue;

                internal DateFormatter(DateTimeOffset date, int dateType, Environment env)
                {
                    _date = date;
                    _dateType = dateType;
                    _env = env;
                    _defaultFormat = env.GetDateFormatObject(dateType);
                }

                public string GetAsString()
                {
                    if (_dateType == TemplateDateModel.Unknown)
                    {
                        throw new TemplateModelException(
                            "Can't convert the date to string, because it is not known which parts of the date variable are in use. Use ?date, ?time or ?datetime built-in, or ?string.<format> or ?string(format) built-in with this date.");
                    }
                    if (_cachedValue == null)
                    {
                        _cachedValue = _date.ToString(
                            _defaultFormat, _env.GetLocale().DateTimeFormat);
                    }
                    return _cachedValue;
                }

                public ITemplateModel Get(string key)
                {
                    return new SimpleScalar(
                        _date.ToString(_env.GetDateFormatObject(_dateType, key),
                        _env.GetLocale().DateTimeFormat));
                }

                public object Exec(IList<string> arguments)
                {
                    if (arguments.Count != 1)
                    {
                        throw new TemplateModelException(
                            "date?string(...) requires exactly 1 argument.");
                    }
                    return Get(arguments[0]);
                }

                public bool IsEmpty()
                {
                    return false;
                }
            }

            private class BooleanFormatter : ITemplateScalarModel, ITemplateMethodModel
            {
                private readonly ITemplateBooleanModel _boolean;
                private readonly Environment _env;

                internal BooleanFormatter(ITemplateBooleanModel boolean, Environment env)
                {
                    _boolean = boolean;
                    _env = env;
                }

                public string GetAsString()
                {
                    return _boolean is ITemplateScalarModel
                               ? ((ITemplateScalarModel) _boolean).GetAsString()
                               : _env.GetBooleanFormat(_boolean.GetAsBoolean());
                }

                public object Exec(IList<string> arguments)
                {
                    if (arguments.Count != 2)
                    {
                        throw new TemplateModelException(
                            "boolean?string(...) requires exactly "
                            + "2 arguments.");
                    }
                    return new SimpleScalar(arguments[_boolean.GetAsBoolean() ? 0 : 1]);
                }
            }
        }

        private class TrimBuiltIn : StringBuiltins.StringBuiltIn
        {
            protected override ITemplateModel CalculateResult(string s, Environment env)
            {
                return new SimpleScalar(s.Trim());
            }
        }

        private class HtmlBuiltIn : StringBuiltins.StringBuiltIn
        {
            protected override ITemplateModel CalculateResult(string s, Environment env)
            {
                return new SimpleScalar(StringUtil.HtmlEnc(s));
            }
        }

        private class XmlBuiltIn : StringBuiltins.StringBuiltIn
        {
            protected override ITemplateModel CalculateResult(string s, Environment env)
            {
                return new SimpleScalar(StringUtil.XmlEnc(s));
            }
        }

        private class XHtmlBuiltIn : StringBuiltins.StringBuiltIn
        {
            protected override ITemplateModel CalculateResult(string s, Environment env)
            {
                return new SimpleScalar(StringUtil.XHtmlEnc(s));
            }
        }

        private class RtfBi : StringBuiltins.StringBuiltIn
        {
            protected override ITemplateModel CalculateResult(string s, Environment env)
            {
                return new SimpleScalar(StringUtil.RtfEnc(s));
            }
        }

        private class UrlBuiltIn : StringBuiltins.StringBuiltIn
        {
            protected override ITemplateModel CalculateResult(string s, Environment env)
            {
                return new UrlBuiltInResult(s, env);
            }

            private class UrlBuiltInResult : ITemplateScalarModel, ITemplateMethodModel
            {
                private readonly string _target;
                private readonly Environment _env;
                private string _cachedResult;

                internal UrlBuiltInResult(string target, Environment env)
                {
                    _target = target;
                    _env = env;
                }

                public string GetAsString()
                {
                    if (_cachedResult == null)
                    {
                        Encoding cs = _env.GetEffectiveUrlEscapingCharset();
                        if (cs == null)
                        {
                            throw new TemplateModelException(
                                "To do URL encoding, the framework that encloses "
                                + "FreeMarker must specify the output encoding "
                                + "or the URL encoding charset, so ask the "
                                + "programmers to fix it. Or, as a last chance, "
                                + "you can set the url_encoding_charset setting in "
                                + "the template, e.g. "
                                + "<#setting url_escaping_charset='ISO-8859-1'>, or "
                                + "give the charset explicitly to the buit-in, e.g. "
                                + "foo?url('ISO-8859-1').");
                        }
                        try
                        {
                            _cachedResult = StringUtil.UrlEnc(_target, cs);
                        }
                        catch (Exception e)
                        {
                            // NOTE: Was UnsupportedEncodingException
                            throw new TemplateModelException(
                                "Failed to execute URL encoding.", e);
                        }
                    }
                    return _cachedResult;
                }

                public object Exec(IList<string> args)
                {
                    if (args.Count != 1)
                    {
                        throw new TemplateModelException("The \"url\" built-in "
                                                         + "needs exactly 1 parameter, the charset.");
                    }
                    try
                    {
                        return new SimpleScalar(
                            StringUtil.UrlEnc(_target, Encoding.GetEncoding(args[0])));
                    }
                    catch (Exception e)
                    {
                        // NOTE: Was UnsupportedEncodingException
                        throw new TemplateModelException(
                            "Failed to execute URL encoding.", e);
                    }
                }
            }
        }

        private class KeysBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateHashModelEx)
                {
                    ITemplateCollectionModel keys = ((ITemplateHashModelEx) model).Keys();
                    AssertNonNull(keys, this, env);
                    if (!(keys is ITemplateSequenceModel))
                        keys = new CollectionAndSequence(keys);
                    return keys;
                }
                throw InvalidTypeException(model, Target, env, "extended hash");
            }
        }

        private class ValuesBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateHashModelEx)
                {
                    ITemplateCollectionModel values = ((ITemplateHashModelEx) model).Values();
                    AssertNonNull(values, this, env);
                    if (!(values is ITemplateSequenceModel))
                    {
                        values = new CollectionAndSequence(values);
                    }
                    return values;
                }
                throw InvalidTypeException(model, Target, env, "extended hash");
            }
        }

        private class SizeBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateSequenceModel)
                {
                    int size = ((ITemplateSequenceModel) model).Size();
                    return new SimpleNumber(size);
                }
                if (model is ITemplateHashModelEx)
                {
                    int size = ((ITemplateHashModelEx) model).Size();
                    return new SimpleNumber(size);
                }
                throw InvalidTypeException(model, Target, env, "extended-hash or sequence");
            }
        }

        private class ExistsBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                try
                {
                    ITemplateModel model = Target.GetAsTemplateModel(env);
                    return model == null ? TemplateBooleanModel.False : TemplateBooleanModel.True;
                }
                catch (InvalidReferenceException)
                {
                    if (Target is ParentheticalExpression)
                    {
                        return TemplateBooleanModel.False;
                    }
                    throw;
                }
            }

            internal override bool IsTrue(Environment env)
            {
                return _getAsTemplateModel(env) == TemplateBooleanModel.True;
            }
        }

        private class HasContentBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                try
                {
                    ITemplateModel model = Target.GetAsTemplateModel(env);
                    return IsEmpty(model)
                               ? TemplateBooleanModel.False
                               : TemplateBooleanModel.True;
                }
                catch (InvalidReferenceException)
                {
                    if (Target is ParentheticalExpression)
                    {
                        return TemplateBooleanModel.False;
                    }
                    throw;
                }
            }

            internal override bool IsTrue(Environment env)
            {
                return _getAsTemplateModel(env) == TemplateBooleanModel.True;
            }
        }

        private class IfExistsBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                try
                {
                    ITemplateModel model = Target.GetAsTemplateModel(env);
                    return model ?? TemplateModel.Nothing;
                }
                catch (InvalidReferenceException)
                {
                    if (Target is ParentheticalExpression)
                    {
                        return TemplateModel.Nothing;
                    }
                    throw;
                }
            }
        }

        private class IsStringBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateScalarModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsNumberBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateNumberModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsNodeBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateNodeModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsBooleanBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateBooleanModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsDateBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateDateModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsMethodBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateMethodModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsMacroBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is Macro)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsTransformBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateTransformModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsHashBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateHashModel) ? TemplateBooleanModel.True : TemplateBooleanModel.False;
            }
        }

        private class IsHashExBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateHashModelEx) ? TemplateBooleanModel.True : TemplateBooleanModel.False;
            }
        }

        private class IsSequenceBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateSequenceModel) ? TemplateBooleanModel.True : TemplateBooleanModel.False;
            }
        }

        private class IsCollectionBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateCollectionModel) ? TemplateBooleanModel.True : TemplateBooleanModel.False;
            }
        }

        private class IsIndexableBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateSequenceModel) ? TemplateBooleanModel.True : TemplateBooleanModel.False;
            }
        }

        private class IsEnumerableBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateSequenceModel || tm is ITemplateCollectionModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class IsDirectiveBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                AssertNonNull(tm, Target, env);
                return (tm is ITemplateTransformModel || tm is Macro || tm is ITemplateDirectiveModel)
                           ? TemplateBooleanModel.True
                           : TemplateBooleanModel.False;
            }
        }

        private class NamespaceBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel tm = Target.GetAsTemplateModel(env);
                if (!(tm is Macro))
                {
                    InvalidTypeException(tm, Target, env, "macro");
                }
                return env.GetMacroNamespace((Macro) tm);
            }
        }

        private class DefaultBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                try
                {
                    ITemplateModel model = Target.GetAsTemplateModel(env);
                    return
                        model == null
                            ? _firstNonNullMethod
                            : new ConstantMethod(model);
                }
                catch (InvalidReferenceException)
                {
                    if (Target is ParentheticalExpression)
                    {
                        return _firstNonNullMethod;
                    }
                    throw;
                }
            }

            private class ConstantMethod : ITemplateMethodModelEx
            {
                private readonly ITemplateModel _constant;

                internal ConstantMethod(ITemplateModel constant)
                {
                    _constant = constant;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    return _constant;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }

            /**
             * A method that goes through the arguments one by one and returns
             * the first one that is non-null. If all args are null, returns null.
             */
            private readonly ITemplateMethodModelEx _firstNonNullMethod = new FirstNonNullMethodImpl();

            private class FirstNonNullMethodImpl : ITemplateMethodModelEx
            {
                public object Exec(IList<ITemplateModel> args)
                {
                    if (args.Count == 0)
                    {
                        throw new TemplateModelException(
                            "?default(arg) expects at least one argument.");
                    }
                    ITemplateModel result = null;
                    foreach (ITemplateModel t in args)
                    {
                        result = t;
                        if (result != null)
                        {
                            break;
                        }
                    }
                    return result;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        private class ContainsBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new BuiltInMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly string _s;

                internal BuiltInMethod(string s)
                {
                    _s = s;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    int ln = args.Count;
                    if (ln != 1)
                    {
                        throw new TemplateModelException(
                            "?contains(...) expects one argument.");
                    }

                    object obj = args[0];
                    if (!(obj is ITemplateScalarModel))
                    {
                        throw new TemplateModelException(
                            "?contains(...) expects a string as "
                            + "its first argument.");
                    }
                    string sub = ((ITemplateScalarModel) obj).GetAsString();

                    return
                        (_s.IndexOf(sub) != -1)
                            ?
                                TemplateBooleanModel.True
                            :
                                TemplateBooleanModel.False;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        private class IndexOfBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new BuiltInMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly string _s;

                internal BuiltInMethod(string s)
                {
                    _s = s;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    int fidx;

                    int ln = args.Count;
                    if (ln == 0)
                    {
                        throw new TemplateModelException(
                            "?index_of(...) expects at least one argument.");
                    }
                    if (ln > 2)
                    {
                        throw new TemplateModelException(
                            "?index_of(...) expects at most two arguments.");
                    }

                    object obj = args[0];
                    if (!(obj is ITemplateScalarModel))
                    {
                        throw new TemplateModelException(
                            "?index_of(...) expects a string as "
                            + "its first argument.");
                    }
                    string sub = ((ITemplateScalarModel) obj).GetAsString();

                    if (ln > 1)
                    {
                        obj = args[1];
                        if (!(obj is ITemplateNumberModel))
                        {
                            throw new TemplateModelException(
                                "?index_of(...) expects a number as "
                                + "its second argument.");
                        }
                        fidx = ((ITemplateNumberModel) obj).GetAsNumber().IntValue();
                    }
                    else
                    {
                        fidx = 0;
                    }

                    return new SimpleNumber(_s.IndexOf(sub, fidx));
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        private class LastIndexOfBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new BuiltInMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly string _s;

                internal BuiltInMethod(string s)
                {
                    _s = s;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    int ln = args.Count;
                    if (ln == 0)
                    {
                        throw new TemplateModelException(
                            "?last_index_of(...) expects at least one argument.");
                    }
                    if (ln > 2)
                    {
                        throw new TemplateModelException(
                            "?last_index_of(...) expects at most two arguments.");
                    }

                    object obj = args[0];
                    if (!(obj is ITemplateScalarModel))
                    {
                        throw new TemplateModelException(
                            "?last_index_of(...) expects a string as "
                            + "its first argument.");
                    }
                    string sub = ((ITemplateScalarModel) obj).GetAsString();

                    if (ln <= 1)
                    {
                        return new SimpleNumber(_s.LastIndexOf(sub));
                    }

                    obj = args[1];
                    if (!(obj is ITemplateNumberModel))
                    {
                        throw new TemplateModelException(
                            "?last_index_of(...) expects a number as "
                            + "its second argument.");
                    }
                    int fidx = ((ITemplateNumberModel) obj).GetAsNumber().IntValue();
                    return new SimpleNumber(_s.LastIndexOf(sub, fidx));
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        private class StartsWithBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new BuiltInMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly string _s;

                internal BuiltInMethod(string s)
                {
                    _s = s;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    if (args.Count != 1)
                    {
                        throw new TemplateModelException(
                            "?starts_with(...) expects exactly 1 argument.");
                    }

                    object obj = args[0];
                    if (!(obj is ITemplateScalarModel))
                    {
                        throw new TemplateModelException(
                            "?starts_with(...) expects a string argument");
                    }
                    string sub = ((ITemplateScalarModel) obj).GetAsString();

                    return _s.StartsWith(sub)
                               ? TemplateBooleanModel.True
                               : TemplateBooleanModel.False;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        private class EndsWithBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new BuiltInMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly string _s;

                internal BuiltInMethod(string s)
                {
                    _s = s;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    if (args.Count != 1)
                    {
                        throw new TemplateModelException(
                            "?ends_with(...) expects exactly 1 argument.");
                    }

                    object obj = args[0];
                    if (!(obj is ITemplateScalarModel))
                    {
                        throw new TemplateModelException(
                            "?ends_with(...) expects a string argument");
                    }
                    string sub = ((ITemplateScalarModel) obj).GetAsString();

                    return _s.EndsWith(sub)
                               ? TemplateBooleanModel.True
                               : TemplateBooleanModel.False;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        private class LeftPadBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new BuiltInMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly string _s;

                internal BuiltInMethod(string s)
                {
                    _s = s;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    int ln = args.Count;
                    if (ln == 0)
                    {
                        throw new TemplateModelException(
                            "?left_pad(...) expects at least 1 argument.");
                    }
                    if (ln > 2)
                    {
                        throw new TemplateModelException(
                            "?left_pad(...) expects at most 2 arguments.");
                    }

                    object obj = args[0];
                    if (!(obj is ITemplateNumberModel))
                    {
                        throw new TemplateModelException(
                            "?left_pad(...) expects a number as "
                            + "its 1st argument.");
                    }
                    int width = ((ITemplateNumberModel) obj).GetAsNumber().IntValue();

                    if (ln == 1)
                    {
                        return new SimpleScalar(StringUtil.LeftPad(_s, width));
                    }

                    obj = args[1];
                    if (!(obj is ITemplateScalarModel))
                    {
                        throw new TemplateModelException(
                            "?left_pad(...) expects a string as "
                            + "its 2nd argument.");
                    }
                    string filling = ((ITemplateScalarModel) obj).GetAsString();
                    try
                    {
                        return new SimpleScalar(StringUtil.LeftPad(_s, width, filling));
                    }
                    catch (ArgumentException e)
                    {
                        if (filling.Length == 0)
                        {
                            throw new TemplateModelException(
                                "The 2nd argument of ?left_pad(...) "
                                + "can't be a 0 length string.");
                        }
                        throw new TemplateModelException(
                            "Error while executing the ?left_pad(...) "
                            + "built-in.", e);
                    }
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        private class RightPadBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new BuiltInMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly string _s;

                internal BuiltInMethod(string s)
                {
                    _s = s;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    int ln = args.Count;
                    if (ln == 0)
                    {
                        throw new TemplateModelException(
                            "?right_pad(...) expects at least 1 argument.");
                    }
                    if (ln > 2)
                    {
                        throw new TemplateModelException(
                            "?right_pad(...) expects at most 2 arguments.");
                    }

                    object obj = args[0];
                    if (!(obj is ITemplateNumberModel))
                    {
                        throw new TemplateModelException(
                            "?right_pad(...) expects a number as "
                            + "its 1st argument.");
                    }
                    int width = ((ITemplateNumberModel) obj).GetAsNumber().IntValue();

                    if (ln == 1)
                    {
                        return new SimpleScalar(StringUtil.RightPad(_s, width));
                    }

                    obj = args[1];
                    if (!(obj is ITemplateScalarModel))
                    {
                        throw new TemplateModelException(
                            "?right_pad(...) expects a string as "
                            + "its 2nd argument.");
                    }
                    string filling = ((ITemplateScalarModel) obj).GetAsString();
                    try
                    {
                        return new SimpleScalar(StringUtil.RightPad(_s, width, filling));
                    }
                    catch (ArgumentException e)
                    {
                        if (filling.Length == 0)
                        {
                            throw new TemplateModelException(
                                "The 2nd argument of ?right_pad(...) "
                                + "can't be a 0 length string.");
                        }
                        throw new TemplateModelException(
                            "Error while executing the ?right_pad(...) "
                            + "built-in.", e);
                    }
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }
    }
}